home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue56 / Splat / zwave.pas < prev   
Encoding:
Pascal/Delphi Source File  |  2000-02-25  |  16.2 KB  |  539 lines

  1. unit ZWave;
  2.  
  3. interface
  4.  
  5. // Simple routines for working with ZLIB-compressed WAVE files and resources.
  6. // Compress compresses a WAV file or stream.
  7. // PlayCompressedSound plays back a compressed WAV file or resource.
  8. //
  9. // PlayCompressedSound works just like PlaySound. See the Windows API
  10. // documentation for details.
  11. //
  12. // The resource type to use for compresses WAVE files is 'ZWAVE', which is
  13. // the value of the zwaveResourceType constant. The ZWAVE compressed contents
  14. // starts with a 4-byte length of the compressed data, followed by the
  15. // actual compressed data.
  16. //
  17. // ZLIB-compressed WAV files tend to be about 50-60% of the size of the
  18. // original WAV file.
  19. //
  20. // Copyright ⌐ 1999 Tempest Software, Inc.
  21.  
  22. uses Windows, SysUtils, Classes;
  23.  
  24. // The arguments are similar to PlaySound.
  25. function PlayCompressedSound(pszSound: PChar; hmod: HINST; fdwSound: Cardinal): LongBool; overload;
  26. // As a convenience for Delphi programs, specify a file name or resource
  27. // name as a Delphi string instead of a PChar.
  28. function PlayCompressedSound(const strSound: string; hmod: HINST; fdwSound: Cardinal): LongBool; overload;
  29.  
  30. // Compress a WAV file or stream.
  31. // Compress a stream, starting from its current position and extending
  32. // to the end of the stream. Write to the output stream, starting at
  33. // the current position.
  34. procedure Compress(InStream, OutStream: TStream); overload;
  35. // Compress an input stream, overwriting the output file with the compressed
  36. // ZWAV results.
  37. procedure Compress(InStream: TStream; const OutFile: string); overload;
  38. // Open a WAV file and compress its contents, writing to a ZWAV file.
  39. procedure Compress(const InFile, OutFile: string); overload;
  40. // Open a WAV file and compress its contents, overwriting an output file.
  41. // The output file has the same name as the input file, but with the '.zwav'
  42. // extension.
  43. procedure Compress(const InFile: string); overload;
  44.  
  45. // Compress all .WAV files in a directory.
  46. procedure CompressDirectory(const DirName: string);
  47.  
  48. // Enumerate all ZWAVE resources. The caller supplies the callback function
  49. // whose signature must match TZWaveEnumFunc. The function returns True
  50. // to continue enumerating resources or False to stop.
  51. type
  52.   TZWaveEnumFunc = function(hmod: HINST; ResName: PChar): Boolean of object;
  53.  
  54. // Return True after enumerating all resources, or False if EnumFunc
  55. // stopped early by returning False.
  56. function EnumZWaveResources(hmod: HINST; EnumFunc: TZWaveEnumFunc): Boolean;
  57.  
  58. type
  59.   // A ZWAVE file or resource has the following format: the first four
  60.   // bytes contain the size of the compressed data, which follow immediately
  61.   // after the size. Resource should always contain an explicit size field
  62.   // because Windows pads resource data to fit on longword boundaries.
  63.   PZWaveData = ^TZWaveData;
  64.   TZWaveData = packed record
  65.     Size: 0..MaxInt;
  66.     Data: TByteArray;
  67.   end;
  68.  
  69.   PZWaveCacheNode = ^TZWaveCacheNode;
  70.   TZWaveCacheNode = record
  71.     // Save the arguments to PlayCompressedSound to look for a match.
  72.     pszSound: Pointer;
  73.     strSound: string;
  74.     hmod: HINST;
  75.     fdwSound: DWORD;
  76.     Data: PZWaveData;
  77.     Next: PZWaveCacheNode;
  78.     Prev: PZWaveCacheNode;
  79.   end;
  80.  
  81.   // Cache the most recently used ZWAVE data, to avoid repeatedly
  82.   // uncompressing the same ZWAVE file or resource. Zero means no caching
  83.   // (except that Snd_Async requires a cache size of at least 1).
  84.   // The cache is searched linearly, so don't use a large cache size.
  85.   // To avoid problems, the maximum size is set arbitrarily to 100.
  86.   TZWaveCacheSize = 0..100;
  87.  
  88.   TZWaveCache = class
  89.   private
  90.     fHead, fTail: PZWaveCacheNode;
  91.     fCount: TZWaveCacheSize;
  92.     fCapacity: TZWaveCacheSize;
  93.     procedure SetCapacity(NewCapacity: TZWaveCacheSize);
  94.   protected
  95.     function Invariant: Boolean;
  96.     procedure Add(pszSound: PChar; hmod: HINST; fdwSound: DWORD;
  97.       Buffer: PZWaveData);
  98.     procedure FreeNode(Node: PZWaveCacheNode);
  99.     function Lookup(pszSound: PChar; hmod: HINST;
  100.       fdwSound: DWORD): PZWaveData;
  101.  
  102.     property Head: PZWaveCacheNode read fHead;
  103.     property Tail: PZWaveCacheNode read fTail;
  104.   public
  105.     constructor Create;
  106.     destructor Destroy; override;
  107.  
  108.     property Count: TZWaveCacheSize read fCount;
  109.     property Capacity: TZWaveCacheSize read fCapacity write SetCapacity default 1;
  110.   end;
  111.  
  112. const
  113.   zwaveResourceType = 'ZWAVE';
  114.  
  115. var
  116.   Cache: TZWaveCache;
  117.  
  118. implementation
  119.  
  120. uses MMSystem, ZLib;
  121.  
  122. { TZWaveCache }
  123.  
  124. constructor TZWaveCache.Create;
  125. begin
  126.   inherited;
  127.   // Default capacity is 1 so a sound that is played with the Snd_ASync flag
  128.   // does not get freed prematurely.
  129.   // Set the Capacity to zero only if the Snd_Sync flag is always used.
  130.   fCapacity := 1;
  131. end;
  132.  
  133. destructor TZWaveCache.Destroy;
  134. begin
  135.   // Set the capacity to zero to free all cached sound buffers.
  136.   Capacity := 0;
  137.   inherited;
  138. end;
  139.  
  140. // Return True if the sound source is actually a string, that is,
  141. // a file name or resource name (but not an integer resource ID).
  142. function IsString(pszSound: PChar; fdwSound: DWORD): Boolean;
  143. begin
  144.   if (fdwSound and Snd_FileName) = Snd_FileName then
  145.     Result := True
  146.   else if (fdwSound and Snd_Resource) <> Snd_Resource then
  147.     Result := False
  148.   else
  149.     Result := LongRec(pszSound).Hi <> 0;
  150. end;
  151.  
  152. // Insert an entry at the head of the cache list. If the cache is too big,
  153. // remove an item from the end.
  154. procedure TZWaveCache.Add(pszSound: PChar; hmod: HINST; fdwSound: DWORD; Buffer: PZWaveData);
  155. var
  156.   Node: PZWaveCacheNode;
  157. begin
  158.   Assert(Invariant);
  159.   New(Node);
  160.   if IsString(pszSound, fdwSound) then
  161.   begin
  162.     Node.pszSound := nil;
  163.     Node.strSound := pszSound;
  164.   end
  165.   else
  166.     Node.pszSound := pszSound;
  167.   Node.hmod := hmod;
  168.   Node.fdwSound := fdwSound;
  169.   Node.Data := Buffer;
  170.  
  171.   Node.Prev := nil;
  172.   Node.Next := Head;
  173.   if Tail = nil then
  174.     fTail := Node;
  175.   if Head <> nil then
  176.     Head.Prev := Node;
  177.   fHead := Node;
  178.  
  179.   if Count < Capacity then
  180.     Inc(fCount)
  181.   else
  182.   begin
  183.     // Cache capacity has been reached, so drop one item from
  184.     // the end of the cache.
  185.     Node := Tail;
  186.     fTail := Tail.Prev;
  187.     if Tail = nil then
  188.       fHead := nil
  189.     else
  190.       Tail.Next := nil;
  191.     Assert(Invariant);
  192.     FreeNode(Node);
  193.   end;
  194. end;
  195.  
  196. // Free a cache node. If the data were loaded from a file, free the data, too.
  197. procedure TZWaveCache.FreeNode(Node: PZWaveCacheNode);
  198. begin
  199.   if (Snd_FileName and Node.fdwSound) = Snd_FileName then
  200.     FreeMem(Node.Data);
  201.   Dispose(Node);
  202. end;
  203.  
  204. // Invariant is an expression that is always true when any method
  205. // starts or returns.
  206. function TZWaveCache.Invariant: Boolean;
  207. begin
  208.   Result := (Count <= Capacity) and
  209.             (((Count = 0) and (Head = nil) and (Tail = nil)) or
  210.              ((Count = 1) and (Head = Tail) and (Head.Next = nil) and (Head.Prev = nil)) or
  211.              ((Count = 2) and (Head <> nil) and (Tail <> nil) and (Head.Next = Tail) and (Head.Prev = nil) and (Tail.Next = nil) and (Tail.Prev = Head)) or
  212.              ((Count > 2) and (Head <> nil) and (Tail <> nil) and (Head.Prev = nil) and (Tail.Next = nil)));
  213. end;
  214.  
  215. function SameSound(Node: PZWaveCacheNode; pszSound: PChar; hmod: HINST; fdwSound: DWORD): Boolean;
  216. begin
  217.   if Node.fdwSound <> fdwSound then
  218.     // Flags must match exactly.
  219.     Result := False
  220.   else if Node.hmod <> hmod then
  221.     // Module handle must match exactly. If the handle is not used,
  222.     // the caller must use 0.
  223.     Result := False
  224.   else if (Node.pszSound = nil) and (Node.strSound <> '') then
  225.     // If the stored sound has a string name (file or resource name),
  226.     // compare the strings.
  227.     Result := SameText(Node.strSound, pszSound)
  228.  
  229.   else
  230.     // Otherwise, the sound pointer is a resource ID or memory pointer,
  231.     // both of which can be compared verbatim.
  232.     Result := pszSound = Node.pszSound
  233. end;
  234.  
  235. // Look up an item in the cache and returns its data. If found,
  236. // move the node to the head of the list--to keep track of
  237. // which item is most-recently used.
  238. function TZWaveCache.Lookup(pszSound: PChar; hmod: HINST; fdwSound: DWORD): PZWaveData;
  239. var
  240.   Node: PZWaveCacheNode;
  241. begin
  242.   Assert(Invariant);
  243.   Node := Head;
  244.   while Node <> nil do
  245.   begin
  246.     if SameSound(Node, pszSound, hmod, fdwSound) then
  247.     begin
  248.       Result := Node.Data;
  249.       // Move this node to the head of the list.
  250.       if Node.Prev <> nil then
  251.       begin
  252.         Assert((Head <> nil) and (Tail <> nil));
  253.         // Node is not already at the head of the list.
  254.         // First remove Node from its position in the list.
  255.         Node.Prev.Next := Node.Next;
  256.         if Node.Next = nil then
  257.           fTail := Node.Prev
  258.         else
  259.           Node.Next.Prev := Node.Prev;
  260.         // Then insert Node at the head of the list.
  261.         Node.Prev := nil;
  262.         Node.Next := Head;
  263.         Head.Prev := Node;
  264.         fHead := Node;
  265.       end;
  266.       Assert(Invariant);
  267.       Exit;
  268.     end;
  269.     Node := Node.Next;
  270.   end;
  271.   Result := nil;
  272. end;
  273.  
  274. // Alter the cache capacity. If the capacity is smaller than the current
  275. // count, drop excess items from the least-recently used end of the cache.
  276. procedure TZWaveCache.SetCapacity(NewCapacity: TZWaveCacheSize);
  277. var
  278.   Node: PZWaveCacheNode;
  279. begin
  280.   Assert(Invariant);
  281.   // If the new size is smaller than the current size,
  282.   // get rid of the end of the cache.
  283.   while Count > NewCapacity do
  284.   begin
  285.     Node := Tail;
  286.     // Make sure the cache pointers remain correct--just in case
  287.     // FreeNode raises an exception.
  288.     if Tail.Prev = nil then
  289.       fHead := nil
  290.     else
  291.       Tail.Prev.Next := nil;
  292.     fTail := Tail.Prev;
  293.     Dec(fCount);
  294.     FreeNode(Node);
  295.   end;
  296.   fCapacity := NewCapacity;
  297.   Assert(Invariant);
  298. end;
  299.  
  300.  
  301. // Get the contents of a ZWAVE file and allocate memory to store the contents,
  302. // setting Buffer to point to the data. The caller must free Buffer.
  303. function GetFileContents(const FileName: string; var Buffer: PZWaveData): Boolean;
  304. var
  305.   FileStream: TFileStream;
  306. begin
  307.   Buffer := nil;
  308.   try
  309.     FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  310.   except
  311.     on EFOpenError do
  312.     begin
  313.       Result := False;
  314.       Exit;
  315.     end;
  316.     else
  317.       raise;
  318.   end;
  319.  
  320.   try
  321.     GetMem(Buffer, FileStream.Size);
  322.     FileStream.ReadBuffer(Buffer^, FileStream.Size);
  323.   finally
  324.     FileStream.Free;
  325.   end;
  326.   Result := True;
  327. end;
  328.  
  329. // Get a ZWAVE resource in Buffer. The caller must NOT free Buffer--Windows
  330. // takes care of deallocating resources automatically.
  331. // The resource type is 'ZWAVE'.
  332. function GetResourceContents(hMod: HINST; ResName: PChar; var Buffer: PZWaveData): Boolean;
  333. var
  334.   ResInfo: HRSRC;
  335.   ResData: HGlobal;
  336. begin
  337.   Result := False;
  338.   ResInfo := FindResource(hmod, ResName, zwaveResourceType);
  339.   if ResInfo = 0 then
  340.     Exit;
  341.   ResData := LoadResource(hmod, ResInfo);
  342.   if ResData = 0 then
  343.     Exit;
  344.   Buffer := LockResource(ResData);
  345.   if Buffer = nil then
  346.     Exit;
  347.   Result := True;
  348. end;
  349.  
  350. // Convenience routine to call FreeMem and set a pointer to nil.
  351. procedure FreeAndNilMem(var P);
  352. var
  353.   Tmp: Pointer;
  354. begin
  355.   Tmp := Pointer(P);
  356.   Pointer(P) := nil;
  357.   FreeMem(Tmp);
  358. end;
  359.  
  360. // Load the sound data and decompress it into OutBuf.
  361. // Return True for success, False if the sound resource or file
  362. // could not be loaded.
  363. function LoadSound(pszSound: PChar; hmod: HINST; fdwSound: Cardinal; var OutBuf: Pointer): Boolean;
  364. var
  365.   InBuf: PZWaveData;
  366.   FreeInBuf: Boolean;
  367.   OutSize: Integer;
  368. begin
  369.   InBuf := nil;
  370.   OutBuf := nil;
  371.   FreeInBuf := False;
  372.   try
  373.     if (fdwSound and Snd_FileName) = Snd_FileName then
  374.     begin
  375.       // pszSound is a file name. PlayCompressedSound must free InBuf.
  376.       if not GetFileContents(pszSound, InBuf) then
  377.       begin
  378.         Result := False;
  379.         Exit;
  380.       end;
  381.       FreeInBuf := True;
  382.     end
  383.     else if (fdwSound and Snd_Resource) = Snd_Resource then
  384.     begin
  385.       // pszSound is a resource name. Windows takes care of freeing
  386.       // InBuf, so PlayCompressedSound must not free it.
  387.       if not GetResourceContents(hmod, pszSound, InBuf) then
  388.       begin
  389.         Result := False;
  390.         Exit;
  391.       end;
  392.     end
  393.     else if (fdwSound and Snd_Memory) = Snd_Memory then
  394.     begin
  395.       // pszSound points to the sound data in memory. PlayCompressedSound
  396.       // must not free the memory--that is the caller's responsibility.
  397.       InBuf := PZWaveData(pszSound);
  398.     end
  399.     else
  400.     begin
  401.       // Must be a registry alias (Snd_Alias), or something else, such as
  402.       // Snd_Purge. Let PlaySound handle this case. In particular,
  403.       // aliases cannot be compressed because they are used by other programs
  404.       // that don't know about ZWAVEs.
  405.       Result := PlaySound(pszSound, hmod, fdwSound);
  406.       Exit;
  407.     end;
  408.  
  409.     // Decompress the data. The estimated size is twice the input size.
  410.     // Most ZWAVE files are about 50-60% of the original size.
  411.     DecompressBuf(@InBuf.Data, InBuf.Size, 2*InBuf.Size, OutBuf, OutSize);
  412.     // Remember this sound.
  413.     Cache.Add(pszSound, hmod, fdwSound, OutBuf);
  414.   finally
  415.     if FreeInBuf then
  416.       FreeMem(InBuf);
  417.   end;
  418.   Result := True;
  419. end;
  420.  
  421. // Look up a compressed sound in the cache. If it isn't present, load
  422. // and decompress the sound data. Then play the decompressed sound.
  423. function PlayCompressedSound(pszSound: PChar; hmod: HINST; fdwSound: Cardinal): LongBool;
  424. var
  425.   Buffer: Pointer;
  426. begin
  427.   Buffer := Cache.Lookup(pszSound, hmod, fdwSound);
  428.   if Buffer = nil then
  429.   begin
  430.     Result := LoadSound(pszSound, hmod, fdwSound, Buffer);
  431.     if Buffer = nil then
  432.       Exit;
  433.   end;
  434.  
  435.   // Play the sound from memory.
  436.   fdwSound := (fdwSound and not (Snd_Resource or Snd_FileName)) or Snd_Memory;
  437.   Result := PlaySound(Buffer, 0, fdwSound);
  438. end;
  439.  
  440. function PlayCompressedSound(const strSound: string; hmod: HINST; fdwSound: Cardinal): LongBool;
  441. begin
  442.   Result := PlayCompressedSound(PChar(strSound), hmod, fdwSound);
  443. end;
  444.  
  445. // Compress the WAVE data from InStream onto OutStream.
  446. procedure Compress(InStream, OutStream: TStream);
  447. var
  448.   InBuffer, OutBuffer: Pointer;
  449.   OutSize: LongInt;
  450. begin
  451.   InBuffer := nil;
  452.   OutBuffer := nil;
  453.   try
  454.     GetMem(InBuffer, InStream.Size);
  455.     InStream.ReadBuffer(InBuffer^, InStream.Size);
  456.     CompressBuf(InBuffer, InStream.Size, OutBuffer, OutSize);
  457.     OutStream.WriteBuffer(OutSize, SizeOf(OutSize));
  458.     OutStream.WriteBuffer(OutBuffer^, OutSize);
  459.   finally
  460.     FreeMem(InBuffer);
  461.     FreeMem(OutBuffer);
  462.   end;
  463. end;
  464.  
  465. procedure Compress(InStream: TStream; const OutFile: string);
  466. var
  467.   OutStream: TFileStream;
  468. begin
  469.   OutStream := TFileStream.Create(OutFile, fmCreate);
  470.   try
  471.     Compress(InStream, OutStream);
  472.   finally
  473.     OutStream.Free;
  474.   end;
  475. end;
  476.  
  477. procedure Compress(const InFile, OutFile: string);
  478. var
  479.   InStream, OutStream: TFileStream;
  480. begin
  481.   InStream := nil;
  482.   OutStream := nil;
  483.   try
  484.     InStream := TFileStream.Create(InFile, fmOpenRead or fmShareDenyWrite);
  485.     OutStream := TFileStream.Create(OutFile, fmCreate);
  486.     Compress(InStream, OutStream);
  487.   finally
  488.     InStream.Free;
  489.     OutStream.Free;
  490.   end;
  491. end;
  492.  
  493. procedure Compress(const InFile: string);
  494. begin
  495.   Compress(InFile, ChangeFileExt(InFile, '.zwav'));
  496. end;
  497.  
  498. // Compress all .WAV files in a directory.
  499. procedure CompressDirectory(const DirName: string);
  500. var
  501.   Search: TSearchRec;
  502.   Path: string;
  503. begin
  504.   Path := IncludeTrailingBackslash(DirName);
  505.   if FindFirst(Path + '*.wav', faAnyFile, Search) = 0 then
  506.     try
  507.       repeat
  508.         Compress(Path + Search.Name);
  509.       until FindNext(Search) <> 0;
  510.     finally
  511.       FindClose(Search);
  512.     end;
  513. end;
  514.  
  515.  
  516. // TZWaveEnumFunc is a TMethod, which doesn't fit into an LParam, so pass
  517. // the address of the method record, that is, PZWaveEnumFunc.
  518. type
  519.   PZWaveEnumFunc = ^TZWaveEnumFunc;
  520.  
  521. function EnumZWave(hmod: HINST; ResType, ResName: PChar;
  522.     EnumFunc: PZWaveEnumFunc): LongBool; stdcall;
  523. begin
  524.   Result := EnumFunc^(hmod, ResName);
  525. end;
  526.  
  527. // Enumerate all the ZWAVE resources.
  528. function EnumZWaveResources(hmod: HINST; EnumFunc: TZWaveEnumFunc): Boolean;
  529. begin
  530.   Result := EnumResourceNames(hmod, zwaveResourceType, @EnumZWave, LParam(@@EnumFunc));
  531. end;
  532.  
  533. initialization
  534.   Cache := TZWaveCache.Create;
  535. finalization
  536.   Cache.Free;
  537. end.
  538.  
  539.